﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.ServiceModel.Channels;
using System.Text;
using System.Threading.Tasks;

namespace WJP.DataPortal.Helper
{
    internal class CompactMessageEncoder : MessageEncoder
    {
        /// <summary>Stores the content type name of the message</summary>
        private const string _contentType = "application/x-gzip";

        /// <summary>Holds the inner message encoder (binary or text)</summary>
        private readonly MessageEncoder _innerEncoder;

        /// <summary>Initialize the message encoder with an inner encoder</summary>
        /// <param name="innerEncoder">Binary or Text message encoder to use as an inner encoder</param>
        public CompactMessageEncoder(MessageEncoder innerEncoder)
        {
            this._innerEncoder = innerEncoder;
        }

        /// <summary>Get the content type of the message</summary>
        public override string ContentType
        {
            get
            {
                return "application/x-gzip";
            }
        }

        /// <summary>Get the media type of the encoder</summary>
        public override string MediaType
        {
            get
            {
                return this._innerEncoder.MediaType;
            }
        }

        /// <summary>Get the message version of the encoder</summary>
        public override MessageVersion MessageVersion
        {
            get
            {
                return this._innerEncoder.MessageVersion;
            }
        }

        /// <summary>
        /// Decompress and desearialize array of bytes into a message.
        /// </summary>
        public override Message ReadMessage(ArraySegment<byte> buffer, BufferManager bufferManager, string contentType)
        {
            Message message = this._innerEncoder.ReadMessage(CompactMessageEncoder.DecompressBuffer(buffer, bufferManager), bufferManager);
            message.Properties.Encoder = (MessageEncoder)this;
            return message;
        }

        /// <summary>
        /// Searialize and compress a message into an array of bytes
        /// </summary>
        public override ArraySegment<byte> WriteMessage(Message message, int maxMessageSize, BufferManager bufferManager, int messageOffset)
        {
            return CompactMessageEncoder.CompressBuffer(this._innerEncoder.WriteMessage(message, maxMessageSize, bufferManager, messageOffset), bufferManager, messageOffset);
        }

        /// <summary>Compress a buffer</summary>
        /// <param name="buffer"></param>
        /// <param name="bufferManager"></param>
        /// <param name="messageOffset"></param>
        /// <returns></returns>
        private static ArraySegment<byte> CompressBuffer(ArraySegment<byte> buffer, BufferManager bufferManager, int messageOffset)
        {
            MemoryStream memoryStream = new MemoryStream();
            memoryStream.Write(buffer.Array, 0, messageOffset);
            using (GZipStream gzipStream = new GZipStream((Stream)memoryStream, CompressionMode.Compress, true))
                gzipStream.Write(buffer.Array, messageOffset, buffer.Count);
            byte[] array = memoryStream.ToArray();
            byte[] buffer1 = bufferManager.TakeBuffer(array.Length);
            Array.Copy((Array)array, 0, (Array)buffer1, 0, array.Length);
            bufferManager.ReturnBuffer(buffer.Array);
            return new ArraySegment<byte>(buffer1, messageOffset, array.Length - messageOffset);
        }

        /// <summary>Decompress a buffer</summary>
        /// <param name="buffer"></param>
        /// <param name="bufferManager"></param>
        /// <returns></returns>
        private static ArraySegment<byte> DecompressBuffer(ArraySegment<byte> buffer, BufferManager bufferManager)
        {
            MemoryStream memoryStream1 = new MemoryStream(buffer.Array, buffer.Offset, buffer.Count);
            MemoryStream memoryStream2 = new MemoryStream();
            int num1 = 0;
            int num2 = 1024;
            byte[] buffer1 = bufferManager.TakeBuffer(num2);
            using (GZipStream gzipStream = new GZipStream((Stream)memoryStream1, CompressionMode.Decompress))
            {
                while (true)
                {
                    int count = gzipStream.Read(buffer1, 0, num2);
                    if (count != 0)
                    {
                        memoryStream2.Write(buffer1, 0, count);
                        num1 += count;
                    }
                    else
                        break;
                }
            }
            bufferManager.ReturnBuffer(buffer1);
            byte[] array = memoryStream2.ToArray();
            byte[] buffer2 = bufferManager.TakeBuffer(array.Length + buffer.Offset);
            Array.Copy((Array)buffer.Array, 0, (Array)buffer2, 0, buffer.Offset);
            Array.Copy((Array)array, 0, (Array)buffer2, buffer.Offset, array.Length);
            ArraySegment<byte> arraySegment = new ArraySegment<byte>(buffer2, buffer.Offset, array.Length);
            bufferManager.ReturnBuffer(buffer.Array);
            return arraySegment;
        }

        public override Message ReadMessage(Stream stream, int maxSizeOfHeaders, string contentType)
        {
            throw new NotSupportedException("Compression Encoding is not yet supported for streamed communications");
        }

        public override void WriteMessage(Message message, Stream stream)
        {
            throw new NotSupportedException("Compression Encoding is not yet supported for streamed communications");
        }

        [Conditional("LOG")]
        private void LogWrite(string format, params object[] args)
        {
            Console.WriteLine(format, args);
        }
    }
}
